package com.wno704.system.util;

import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.regex.Pattern;

@Slf4j
public class DateUtil {

    private static final String DATE_FORMAT = "yyyy-MM-dd";

    public static final String FULL_MTIME_PATTERN = "yyyyMMddHHmmsss";

    public static final String FULL_TIME_PATTERN = "yyyyMMddHHmmss";

    public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    public static final String CST_TIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";

    private DateUtil(){

    }

    /**
     * String 转 Date,默认字符串格式为自定义。
     *
     * @param dateStr 日期字符串
     * @param patten 字符串日期格式
     * @return Date
     */
    public static Date strToDt(String dateStr, String patten) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(patten);
        return sdf.parse(dateStr);
    }

    /**
     * String 转 Date,默认字符串为"yyyy-MM-dd HH:mm:ss"格式。
     *
     * @param dateStr 日期字符串
     * @return Date
     */
    public static Date strToDt(String dateStr) throws ParseException {
        return strToDt(dateStr, FULL_TIME_SPLIT_PATTERN);
    }

    /**
     * Date 转 String,返回字符串格式为自定义。
     *
     * @param date 日期字符串
     * @param patten 字符串日期格式
     * @return String
     */
    public static String dtToStr(Date date, String patten) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(patten, Locale.CHINA);
        return simpleDateFormat.format(date);
    }

    public static String dtToStr(Date date) {
        return dtToStr(date,FULL_TIME_SPLIT_PATTERN);
    }

    public static String formatNowDate(String pattern) {
        return dtToStr(new Date(),pattern);
    }

    public static String formatCstTime(String date, String format) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(CST_TIME_PATTERN, Locale.US);
        Date usDate = simpleDateFormat.parse(date);
        return dtToStr(usDate, format);
    }

    public static String formatInstant(Instant instant, String format) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return localDateTime.format(DateTimeFormatter.ofPattern(format));
    }

    public static String getStartDate(Date date) {
        return dtToStr(date,"yyyy-MM-dd 00:00:00");
    }

    public static String getEndDate(Date date) {
        return dtToStr(date,"yyyy-MM-dd 23:59:59");
    }

    public static Long getUniqueNumber() {
        SimpleDateFormat sdf = new SimpleDateFormat(FULL_MTIME_PATTERN);
        return Long.parseLong(sdf.format(new Date()));
    }

    /**
     * LocalDate 转 Date
     *
     * @param localDate
     * @return
     */
    public static Date ldToDt(LocalDate localDate) {
        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    /**
     * LocalDateTime 转 Date
     *
     * @param localDateTime
     * @return
     */
    public static Date ldtToDt(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * Date 转 LocalDate
     *
     * @param date
     * @return
     */
    public static LocalDate dtToLd(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * Date 转 LocalDateTime
     *
     * @param date
     * @return
     */
    public static LocalDateTime dtToLdt(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * Date 转 LocalDateTime
     *
     * @param offset unit
     * @return ChronoUnit.YEARS 年
     * ChronoUnit.MONTHS 月
     * ChronoUnit.DAYS) 天
     * ChronoUnit.WEEKS 周
     * ChronoUnit.HOURS 小时
     * ChronoUnit.MINUTES 分钟
     * ChronoUnit.SECONDS 秒
     * ChronoUnit.HALF_DAYS 半天
     * ChronoUnit.CENTURIES 一个世纪
     * ChronoUnit.DECADES 10年
     * ChronoUnit.NANOS 纳秒
     * ChronoUnit.MICROS 微秒
     * ChronoUnit.MILLIS 毫秒
     * ChronoUnit.MILLENNIA 千年
     * ChronoUnit.FOREVER 永恒(此处不可用)
     * ChronoUnit.ERAS 一个时代(此处不可用)
     */
    public static LocalDateTime getOffSetLocalDateTime(Long offset, ChronoUnit unit) {
        LocalDateTime localDateTime = LocalDateTime.now();
        return localDateTime.plus(offset, unit);
    }

    /**
     * localDate 转时间戳
     *
     * @param localDate
     * @return
     */
    public static long toMilli(LocalDate localDate) {
        return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    /**
     * LocalDateTime 转时间戳
     *
     * @param localDateTime
     * @return
     */
    public static long toMilli(LocalDateTime localDateTime) {
        return localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
    }

    /**
     * LocalDate 转字符串
     *
     * @param localDate
     * @return
     */
    public static String ldToStr(LocalDate localDate, String patten) {
        return DateTimeFormatter.ofPattern(patten).format(localDate);
    }

    /**
     * LocalDate 转字符串
     *
     * @param localDate
     * @return
     */
    public static String ldToStr(LocalDate localDate) {
        return  ldToStr(localDate, DATE_FORMAT);
    }

    /**
     * LocalDateTime 转字符串
     *
     * @param localDateTime
     * @return
     */
    public static String ldtToStr(LocalDateTime localDateTime, String patten) {
        return DateTimeFormatter.ofPattern(patten).format(localDateTime);
    }

    /**
     * LocalDateTime 转字符串
     *
     * @param localDateTime
     * @return
     */
    public static String ldtToStr(LocalDateTime localDateTime) {
        return ldtToStr(localDateTime, FULL_TIME_SPLIT_PATTERN);
    }

    /**
     * 字符串转 LocalDate
     *
     * @param dateStr
     * @return
     */
    public static LocalDate strToLd(String dateStr, String patten) {
        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(patten));
    }

    /**
     * 字符串转 LocalDate
     *
     * @param dateStr
     * @return
     */
    public static LocalDate strToLd(String dateStr) {
        return strToLd(dateStr, DATE_FORMAT);
    }

    /**
     * 字符串转 LocalDateTime
     *
     * @param dateStr
     * @return
     */
    public static LocalDateTime strToLdt(String dateStr, String patten) {
        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(patten));
    }

    /**
     * 字符串转 LocalDateTime
     *
     * @param dateStr
     * @return
     */
    public static LocalDateTime strToLdt(String dateStr) {
        return strToLdt(dateStr, FULL_TIME_SPLIT_PATTERN);
    }

    /**
     * 获取当月的第一天日期
     *
     * @return
     */
    public static LocalDate getFirstDayOfMonth(LocalDate ld) {
        return ld.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * 获取当月的第一天日期
     *
     * @return
     */
    public static LocalDate getFirstDayOfMonth() {
        return getFirstDayOfMonth(LocalDate.now());
    }

    /**
     * 获取当月的最后一天日期
     *
     * @return
     */
    public static LocalDate getLastDayOfMonth(LocalDate ld) {
        return ld.with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 获取当月的最后一天日期
     *
     * @return
     */
    public static LocalDate getLastDayOfMonth() {
        return getLastDayOfMonth(LocalDate.now());
    }

    /**
     * 获取当前周的周一
     *
     * @return
     */
    public static LocalDate getCurMonday() {
        LocalDate now = LocalDate.now();
        return now.minusDays(now.getDayOfWeek().getValue() - 1);
    }

    /**
     * 获取当前周的周日
     *
     * @return
     */
    public static LocalDate getCurSunday() {
        LocalDate now = LocalDate.now();
        return now.minusDays(now.getDayOfWeek().getValue() - 7);
    }

    /**
     * 填充日期，如果小于 0，填充 0
     *
     * @param value
     * @return
     */
    public static String fillTime(int value) {
        return value < 10 ? "0" + value : value + "";
    }

    /**
     * 时间描述
     *
     * @param dateTime
     * @return
     */
    public static String timeDesc(LocalDateTime dateTime) {
        LocalDateTime now = LocalDateTime.now();
        Duration between = Duration.between(dateTime, now);
        long minutes = between.toMinutes();
        if (minutes < 1) {
            return "刚刚";
        }

        if (minutes < 60) {
            return minutes + "分钟前";
        }

        long hours = between.toHours();
        if (hours < 24) {
            return hours + "小时前";
        }

        long days = between.toDays();
        if (days < 7) {
            return days + "天前";
        }

        if (days < 120) {
            return days / 7 + "周前";
        }

        if (days < 365) {
            return days / 30 + "月前";
        }

        return days / 365 + "年前";
    }

    /**
     * @Description: 以分钟为单位进行统计 type 0 返回在夜间的时间  1 返回剔除夜间的时间
     * @Param: [type, startOrderTime, endOrderTime, startTimeAtNight, endTimeAtNight]
     * @return: long
     * @Author: wno704
     * @Date: 2022/9/29
     */
    public static long calcRoughly(int type, Date startOrderTime, Date endOrderTime, String startTimeAtNight, String endTimeAtNight) {
        long dayTime = 0;
        long nightTime = 0;
        long startTime = startOrderTime.getTime();
        long endTime = endOrderTime.getTime();
        long startCurMinutes = calcCurMinutes(startTimeAtNight);
        long endCurMinutes = calcCurMinutes(endTimeAtNight);
        while (startTime <= endTime) {
            long modTime = ((startTime + 8 * 60 * 60 * 1000) / (1000 * 60)) % (24 * 60);
            if (modTime >= endCurMinutes && modTime < startCurMinutes) {
                dayTime += 1;
            } else {
                nightTime += 1;
            }
            startTime += (1000 * 60);
        }
        if (type == 1)
            return dayTime;
        return nightTime;
    }

    /**
     * @Description: 以分钟为单位进行统计 type 0 返回在夜间的时间  1 返回剔除夜间的时间
     * @Param: [type, startOrderTime, endOrderTime, startTimeAtNight, endTimeAtNight]
     * @return: long
     * @Author: wno704
     * @Date: 2022/9/29
     */
    public static long calcRoughly(int type, LocalDateTime startOrderTime, LocalDateTime endOrderTime, String startTimeAtNight, String endTimeAtNight) {
        long dayTime = 0;
        long nightTime = 0;
        long startTime = startOrderTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long endTime = endOrderTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long startCurMinutes = calcCurMinutes(startTimeAtNight);
        long endCurMinutes = calcCurMinutes(endTimeAtNight);
        while (startTime <= endTime) {
            long modTime = ((startTime + 8 * 60 * 60 * 1000) / (1000 * 60)) % (24 * 60);
            if (modTime >= endCurMinutes && modTime < startCurMinutes) {
                dayTime += 1;
            } else {
                nightTime += 1;
            }
            startTime += (1000 * 60);
        }
        if (type == 1)
            return dayTime;
        return nightTime;
    }

    // 时间字符串 "22:00:00"
    static long calcCurMinutes(String time) {
        String pattern = "\\d{2}:\\d{2}:\\d{2}";
        if (Pattern.matches(pattern, time)) {
            int hour = Integer.valueOf(time.substring(0, 2));
            int minutes = Integer.valueOf(time.substring(3, 5));
            return hour * 60 + minutes;
        }
        return 0;
    }

    /**
     * 这里共有2个时间段（b1-----e1）【b2-----e2】，4个时间点；
     * 相当于两条线段(b代表起点，e代表端点，b<=e)，4个端点。
     * 可分3种情况：
     * 1.包含(两条)
     * 2.相交(两条)
     * 3.不相交
     *
     * @param startTime1
     * @param endTime1
     * @param startTime2
     * @param endTime2
     * @return
     */
    public static long getTimeCoincidence(ChronoUnit unit, Date startTime1, Date endTime1, Date startTime2, Date endTime2) {
        long b1 = startTime1.getTime();
        long e1 = endTime1.getTime();
        long b2 = startTime2.getTime();
        long e2 = endTime2.getTime();
        long coincidenceTime;
        if (b1 <= b2 && e1 >= e2) {//（b1---【b2-----e2】--e1）1包含2
            log.info("1包含2");
            coincidenceTime = getTimeDifference(unit, endTime2, startTime2);
        } else if (b1 >= b2 && e1 <= e2) {//【b2---（b1-----e1）--e2】2包含1
            log.info("2包含1");
            coincidenceTime = getTimeDifference(unit, endTime1, startTime1);
        } else if (b1 >= b2 && b1 <= e2 && e2 <= e1) {//（b1---【b2---e1）----e2】 相交1
            log.info("相交");
            coincidenceTime = getTimeDifference(unit, endTime2, startTime1);
        } else if (b1 <= b2 && e1 <= e2 && e1 >= b2) {//【b2---(b1---e2】----e1) 相交2
            log.info("相交");
            coincidenceTime = getTimeDifference(unit, endTime1, startTime2);
        } else if (e1 <= b2 || b1 >= e2) {//（b1-----e1）【b2-----e2】或【b2-----e2】（b1-----e1）不相交
            coincidenceTime = 0;
        } else {
            coincidenceTime = 0;
            log.info("意料外的日期组合，无法计算重合！");
        }
        return coincidenceTime;
    }

    /*
     * @param startTime1
     * @param endTime1
     * @param startTime2
     * @param endTime2
     * @return
     */
    public static long getTimeCoincidence(ChronoUnit unit, LocalDateTime startTime1, LocalDateTime endTime1, LocalDateTime startTime2, LocalDateTime endTime2) {
        long b1 = startTime1.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long e1 = endTime1.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long b2 = startTime2.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long e2 = endTime2.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long coincidenceTime;
        if (b1 <= b2 && e1 >= e2) {//（b1---【b2-----e2】--e1）1包含2
            log.info("1包含2");
            coincidenceTime = getTimeDifference(unit, endTime2, startTime2);
        } else if (b1 >= b2 && e1 <= e2) {//【b2---（b1-----e1）--e2】2包含1
            log.info("2包含1");
            coincidenceTime = getTimeDifference(unit, endTime1, startTime1);
        } else if (b1 >= b2 && b1 <= e2 && e2 <= e1) {//（b1---【b2---e1）----e2】 相交1
            log.info("相交");
            coincidenceTime = getTimeDifference(unit, endTime2, startTime1);
        } else if (b1 <= b2 && e1 <= e2 && e1 >= b2) {//【b2---(b1---e2】----e1) 相交2
            log.info("相交");
            coincidenceTime = getTimeDifference(unit, endTime1, startTime2);
        } else if (e1 <= b2 || b1 >= e2) {//（b1-----e1）【b2-----e2】或【b2-----e2】（b1-----e1）不相交
            coincidenceTime = 0;
        } else {
            coincidenceTime = 0;
            log.info("意料外的日期组合，无法计算重合！");
        }
        return coincidenceTime;
    }

    /**
     * 计算两个时间差
     *
     * @param unit      计算单位
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 返回时间差
     */
    public static long getTimeDifference(ChronoUnit unit, Date startTime, Date endTime) {
        long subMilliSecond = startTime.getTime() - endTime.getTime();
        if (unit.equals(ChronoUnit.WEEKS)) {
            return subMilliSecond / (1000 * 60 * 60 * 24 * 7);
        }
        if (unit.equals(ChronoUnit.DAYS)) {
            return subMilliSecond / (1000 * 60 * 60 * 24);
        }
        if (unit.equals(ChronoUnit.HOURS)) {
            return subMilliSecond / (1000 * 60 * 60);
        }
        if (unit.equals(ChronoUnit.MINUTES)) {
            return subMilliSecond / (1000 * 60);
        }
        if (unit.equals(ChronoUnit.SECONDS)) {
            return subMilliSecond / 1000;
        }
        return 0;
    }

    /**
     * 计算两个时间差
     *
     * @param unit      计算单位
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 返回时间差
     */
    public static long getTimeDifference(ChronoUnit unit, LocalDateTime startTime, LocalDateTime endTime) {
        long subMilliSecond = startTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() - endTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (unit.equals(ChronoUnit.WEEKS)) {
            return subMilliSecond / (1000 * 60 * 60 * 24 * 7);
        }
        if (unit.equals(ChronoUnit.DAYS)) {
            return subMilliSecond / (1000 * 60 * 60 * 24);
        }
        if (unit.equals(ChronoUnit.HOURS)) {
            return subMilliSecond / (1000 * 60 * 60);
        }
        if (unit.equals(ChronoUnit.MINUTES)) {
            return subMilliSecond / (1000 * 60);
        }
        if (unit.equals(ChronoUnit.SECONDS)) {
            return subMilliSecond / 1000;
        }
        return 0;
    }

    /**
     * 计算两个时间之间的工作日
     *
     * @param startDate 开始时间（yyyy-MM-dd）
     * @param endDate   结束时间（yyyy-MM-dd）
     * @return 返回时间差
     */
    public static long getDutyDays(Date startDate, Date endDate) {
        int result = 0;
        while (startDate.compareTo(endDate) <= 0) {
            if (startDate.getDay() != 6 && startDate.getDay() != 0)
                result++;
            startDate.setDate(startDate.getDate() + 1);
        }
        return result;
    }

    /**
     * 计算两个时间之间的工作日
     *
     * @param startDate 开始时间（yyyy-MM-dd）
     * @param endDate   结束时间（yyyy-MM-dd）
     * @return 返回时间差
     */
    public static long getDutyDays(LocalDate startDate, LocalDate endDate) {
        int result = 0;
        while (startDate.compareTo(endDate) <= 0) {
            if (startDate.getDayOfWeek() != DayOfWeek.SUNDAY && startDate.getDayOfWeek() != DayOfWeek.SATURDAY)
                result++;
            startDate = startDate.plus(1, ChronoUnit.DAYS);
        }

        return result;
    }

    /**
     * @Description: 获取起始日期和结束日期之间的日期列表
     * @Param: [startDate, endDate]
     * @return: java.util.List<java.util.Date>
     * @Author: wno704
     * @Date: 2022/10/2
     */
    public static List<Date> getDaysBetweenDates(Date startDate, Date endDate) {
        List<Date> dates = new ArrayList<>();
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(startDate);

        while (calendar.getTime().before(endDate)) {
            Date result = calendar.getTime();
            dates.add(result);
            calendar.add(Calendar.DATE, 1);
        }
        return dates;
    }

    /**
     * @Description: 获取起始日期和结束日期之间的日期列表
     * @Param: [startDate, endDate]
     * @return: java.util.List<java.util.Date>
     * @Author: wno704
     * @Date: 2022/10/2
     */
    public static List<LocalDate> getDaysBetweenDates(LocalDate startDate, LocalDate endDate) {
        List<LocalDate> dates = new ArrayList<>();
        while (startDate.isBefore(endDate)) {
            dates.add(startDate);
            startDate = startDate.plus(1, ChronoUnit.DAYS);
        }
        return dates;
    }
    
}
